package jdesktop.appbar.win32;

import com.sun.jna.Pointer;

import java.awt.*;
import java.util.logging.Level;
import java.util.logging.Logger;


/**
 * Encapsulates the windows style flags for standard and extended Win32 window styles.
 * The styles are represented as flags on an {@code int}, using {@code WS_xxx} and {@code WS_EX_xxx}
 * constaints from {@link User32}.
 * <p>
 * Sure, we could have made the constants typesafe by having getter/setter methods for each style.
 * @see jdesktop.appbar.win32.WindowStyle.StyleGroup
 */
class WindowStyle
{
    /**
     * The set of extended style flags. Combining {@code WS_EX_xxx} flags using {@code bitwise-OR).
     */
    public static final String EXTENDED = "extendedStyle";

    /**
     * The set of standad style flags. Combining {@code WS_xxx} flags using {@code bitwise-OR).
     */
    public static final String STANDARD = "standardStyle";


    final Logger log = Logger.getLogger(getClass().getName());

    private int standardStyle;
    private int extendedStyle;

    public WindowStyle()
    {
    }

    public WindowStyle(int standardStyle, int extendedStyle)
    {
        this.standardStyle = standardStyle;
        this.extendedStyle = extendedStyle;
    }

    /**
     * Retrieves the {@link #STANDARD standard style} flags.
     * @return  The standard style flags
     *
     */
    public int getStandardStyle()
    {
        return standardStyle;
    }

    /**
     * Link sets the {@link #STANDARD standard style} flags.}
     * @param standardStyle
     */
    public void setStandardStyle(int standardStyle)
    {
        this.standardStyle = standardStyle;
    }


    public int getExtendedStyle()
    {
        return extendedStyle;
    }

    public void setExtendedStyle(int extendedStyle)
    {
        this.extendedStyle = extendedStyle;
    }

    /**
     * Fetches the style flags in a style set.
     * @param styleGroup
     * @return
     */
    public int getStyleSet(StyleGroup styleGroup)
    {
        switch (styleGroup)
        {
            case ORIGINAL: return getStandardStyle();
            case EXTENDED: return getExtendedStyle();
            default:
                throw new IllegalArgumentException();
        }
    }


    public void setStyleSet(StyleGroup styleGroup, int flags)
    {
        switch (styleGroup)
        {
            case ORIGINAL: setStandardStyle(flags); break;
            case EXTENDED: setExtendedStyle(flags); break;
        }
    }

    public void assign(WindowStyle style)
    {
        this.standardStyle = style.standardStyle;
        this.extendedStyle = style.extendedStyle;
    }

    public void assignFromWindow(Window window, Pointer hWnd)
    {
        for (StyleGroup styleGroup : StyleGroup.values())
        {
            int styles = getWindowStyle(hWnd, styleGroup);
            setStyleSet(styleGroup, styles);
        }
    }

    /**
     * Assigns the window style flags to the window. Note, it is the caller's responsibility to
     * call {@link #updateWindow} to ensure the window is redrawn to reflect the style changes.
     * @param window    The window to apply the styles to.
     * @param hWnd      The native window handle of {@code window}.
     */
    public void assignToWindow(Window window, Pointer hWnd)
    {
        for (StyleGroup styleGroup : StyleGroup.values())
        {
            int styles = getStyleSet(styleGroup);
            if (log.isLoggable(Level.FINE))
                log.fine(String.format("Setting window style %s %x", styleGroup.name().toLowerCase(), styles));
            setWindowStyle(window, hWnd, styleGroup, styles);
        }
    }

    /**
     * Modifies a styleGroup by adding and removing flags. The {@code flagsToRemove}
     * are removed first, and then {@code flagsToAdd} are added.
     * @param styleGroup      The styleGroup to change.
     * @param flagsToAdd    Flags to add to the style set. These take precedence if the same
     *  flag is also present in {@code flagsToRemove}.
     * @param flagsToRemove Flags to remove from the style set.
     * @return The new value of the style set.
     */
    public int modifyStyleSet(StyleGroup styleGroup, int flagsToAdd, int flagsToRemove)
    {
        int original = getStyleSet(styleGroup);
        int newStyle = (original & ~flagsToRemove) | flagsToAdd;
        setStyleSet(styleGroup, newStyle);
        return newStyle;
    }

    /**
     * Retrieves the style flags currently set on a {@code HWND}.
     * @param hWnd      The native window handle of the window to retrieve style flags from.
     * @param styleGroup  The {@code }styleGroup} identifying the set of flags to retrieve.
     * @return The set of style flags.
     */
    static public int getWindowStyle(Pointer hWnd, StyleGroup styleGroup)
    {
        final User32 user32 = User32.INSTANCE;
        int stylePos = styleGroup == StyleGroup.EXTENDED ? User32.GWL_EXSTYLE : User32.GWL_STYLE;
        int style = user32.GetWindowLongW(hWnd, stylePos);
        return style;
    }


    /**
     * Sets the styles on the window. Style flags are first removed, and then added.
     *
     * @param styleGroup  The styleGroup to set.
     *  style.
     * @param newStyles  The new style set
     * @return the original style flags
     */
    static public void setWindowStyle(Window window, Pointer hWnd, StyleGroup styleGroup, int newStyles)
    {
        final User32 user32 = User32.INSTANCE;
        boolean extended = styleGroup == StyleGroup.EXTENDED;
        int stylePos = extended ? User32.GWL_EXSTYLE : User32.GWL_STYLE;
        user32.SetWindowLongW(hWnd, stylePos, newStyles);
    }

    /**
     * Updates the window to reflect style changes.
     * @param window
     * @param hWnd
     * @param bigChange
     * @see #setWindowStyle
     * @see #assignToWindow 
     */
    static public void updateWindow(Window window, Pointer hWnd, boolean bigChange)
    {
        User32 user32 = User32.INSTANCE;
        if (bigChange)
        {
            boolean wasVisible = (window.isVisible());
            // typically have to hide the window to remove the titlebar
            //window.setVisible(false);

            // rebuild the frame
            user32.SetWindowPos(hWnd, User32.HWND_TOPMOST, 0, 0, 0, 0,
                            User32.SWP_NOMOVE |
                            User32.SWP_NOSIZE |
                            User32.SWP_NOACTIVATE |
                            User32.SWP_NOZORDER |
                            User32.SWP_DRAWFRAME |
                            User32.SWP_FRAMECHANGED );

            if (wasVisible)
            {
                //window.setVisible(true);
                //user32.UpdateWindow(hWnd);
            }

            user32.DefWindowProcW(hWnd, 0x85, 1, 0);
        }
        else
        {
            user32.UpdateWindow(hWnd);
        }

    }


    @Override
    /**
     * Compares this window style for equality with another object.
     * To be considered eaual, the other object must be an instnace of
     * WindowStyle, and must define the same value for each {@code StyleFlags} type.
     */
    public boolean equals(Object o)
    {
        if (this == o) return true;
        if (!(o instanceof WindowStyle)) return false;

        WindowStyle that = (WindowStyle) o;

        if (extendedStyle != that.extendedStyle) return false;
        if (standardStyle != that.standardStyle) return false;

        return true;
    }

    @Override
    /**
     * Computes the hashcode, based on the standard and extended styles.
     * @return The hash code
     */
    public int hashCode()
    {
        int result = standardStyle;
        result = 31 * result + extendedStyle;
        return result;
    }

    /**
     * Enumeration for the different groups of styles. 
     */
    static public enum StyleGroup
    {
        /**
         * The original window styles. {@code WS_xxx} constants in the Win32 API.
         */
        ORIGINAL,

        /**
         * The extended window styles, {@code WS_EX_xxx} constants in the Win32 API.
         */
        EXTENDED
    }



}
